There is an alternative way using the str functions of pandas.Series, which has the advantage that they are usually faster. In this case you can use pandas.Series.str.replace, which can take a regex to match strings against and a callable to replace them with, which gets passed the regex match object:
def repl(m):
k = m.group()
return k + x[k]
df.q.str.replace(r'\[.*\]', repl)
# 0 hey this is [a](1234)
# 1 why dont you [b](2345)
# 2 clas is [c](xyzad)
# Name: q, dtype: object
This uses the fact that your keys to replace seem to follow a pattern, though, and works only as long as you can write a regex to capture it. In that sense your solution is more general.
One thing that you can change in your approach is the check for if i in data. It is superfluous, since str.replace will just ignore it if the string to replace does not appear in the string (it has to search linearly through the whole string to figure that out, but so does i in data).
In addition, instead of iterating over the keys with for i in x.keys():, you can just do for i in x:. But since you also need the value, you can directly do for key, repl in x.items(): data = data.replace(key, repl).