On a custom react-admin page, I use the Query component to fetch and show data and the withDataProvider decorator to save a mutation:
export const Controller = ({data, dataProvider}: any) => {
...
dataProvider(UPDATE, "users", { id: data.id, data: {...data, newField: "foo" })
...
}
export const Container = ({ data, loading, loaderror, ...props }: any) => {
const ConnectedController = useMemo(() => withDataProvider(Controller), []);
if (loading) { return <p data-testid="loading">Loading</p>; }
if (loaderror) { return <p data-testid="error">Error</p>; }
return <ConnectedController {...props} data={data} />;
}
export const InjectInitialValues = ({ userid, ...props }: InjectProps) => {
return <Query type="GET_ONE" resource="users" payload={{ id: userid }}>
{({ data, loading, error }: { data: object, loading: boolean, error: string }) => (
<Container {...props} data={data} loading={loading} loaderror={error} />
)}
</Query>
};
However, after saving the changes, the query component does not trigger a rerender with the new values. Does anyone know how to plug it together such that query rerenders after a change?
Thank you very much.
Could you add your code? to run re-render you should update state or receive new data via props, also you can run force update, but i am not sure if it will help you. First we should see code
It seems to me that you have a problem in this line, so you set an empty list of dependencies [].
const ConnectedController = useMemo(() => withDataProvider(Controller), []);
When you specify a dependency list through the last argument of the useMemo hook, it should include all the values used,
which are involved in the React data stream, including the pros, state, and their derivatives.
Related
I am trying to make an API call in useEffect() and want useEffect() to be called everytime a new data is added in the backend.
I made a custom Button(AddUserButton.js) which adds a new user in backend. I am importing this button in the file (ManageUsers.js) where I am trying to display all the users. I just wanted to make an useState to keep track everytime an add button is clicked and make useEffect refresh according to it. For Example:
const [counter, setCounter] = useState(0);
...
const handleAdd = () => {
setCounter(state => (state+1));
};
...
useEffect(() => {
// fetch data here
...
}, [counter]);
...
return(
<Button onClick = {handleAdd}> Add User </Button>
);
But currently because I have two .js files, I am not sure how to make my logic stated above
work in this case
ManageUsers.js
import AddUserButton from "./AddUserButton";
...
export default function ManageShades() {
...
useEffect(() => {
axios
.get("/api/v1/users")
.then(function (response) {
// After a successful add, store the returned devices
setUsers(response.data);
setGetUserFailed(false);
})
.catch(function (error) {
// After a failed add
console.log(error);
setGetUserFailed(true);
});
console.log("Load User useeffect call")
},[]);
return (
<div>
...
<Grid item xs={1}>
<AddUserButton title = "Add User" />
</Grid>
...
</div>
);
}
AddUserButton.js
export default function AddDeviceButton() {
...
return (
<div>
<Button variant="contained" onClick={handleClickOpen}>
Add a device
</Button>
...
</div>
);
}
A common approach is to pass a callback function to your button component that updates the state of the parent component.
import { useState, useEffect } from "react";
const AddUserButton = ({ onClick }) => {
return <button onClick={onClick} />;
};
export default function Test() {
const [updateCount, setUpdateCount] = useState(false);
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count++);
}, [updateCount]);
return (
<div>
<AddUserButton
onClick={() => {
// do something, e.g. send data to your API
// finally, trigger update
setUpdateCount(!updateCount);
}}
/>
</div>
);
}
So it seems like you are trying to let a child update it's parent's state, an easy way to do this is to let the parent provide the child a callback, which will update the parent's state when called.
const parent = ()=>{
const [count, setCount] = useState(0);
const increCallback = ()=>{setCount(count + 1)};
return (<div>
<child callback={increCallback}/>
</div>);
}
const child = (callback)=>{
return (<button onClick={callback}/>);
}
If you were to tell the ManageUsers component to fetch from the back-end right after the AddUser event is fired, you will almost certainly not see the latest user in the response.
Why? It will take some time for the new user request to be received by the back-end, a little longer for proper security rules to be passed, a little longer for it to be formatted, sanitized, and placed in the DB, and a little longer for that update to be available for the API to pull from.
What can we do? If you manage the users in state - which it looks like you do, based on the setUsers(response.data) - then you can add the new user directly to the state variable, which will then have the user appear immediately in the UI. Then the new user data is asynchronously added to the back-end in the background.
How can we do it? It's a really simple flow that looks something like this (based roughly on the component structure you have right now)
function ManageUsers() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('https://api.com')
.then(res => res.json())
.then(res => setUsers(res));
.catch(err => console.error(err));
}, [setUsers]);
const handleAdd = ({ name, phone, dob }) => {
const newUser = {
name,
phone,
dob
};
setUsers([...users, newUser]);
};
return (
<div>
<UserList data={users} />
<AddUser add={handleAdd} />
</div>
);
}
// ...
function AddUser({ add }) {
const [userForm, setUserForm] = useState({ name: "", phone: "", dob: "" });
return (
// controlled form fields
<button onClick={() => add(userForm)}>Submit</button>
);
}
// ...
function UserList({ data }) {
return (
<>
{data.map(user =>
<p>{user.name></p>
}
</>
);
}
Once the user adds a new user with the "Submit" button, it passes the new user to the "add" function which has been passed down as a prop. Then the user is appended to the users array of the ManageUsers component, instantly populating the latest user data in the UserList component. If we wait for a new fetch request to come through, this will add a delay, and the newest user we just added will not likely come back with the response.
Redux state is undefined in first render and I returned the initilizedState = {} in my reducer
store.js
const store = createStore(
rootReducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : (f) => f
)
)
export default store
rootReducer.js
const rootReducer = combineReducers({
search: searchReducer,
product: productReducer,
})
export default rootReducer
reducer.js
const initialState = {}
const productReducer = (state = initialState, action) => {
const { type, payload } = action
switch (type) {
case PRODUCTS_ALL:
console.log('reducer')
return { ...state, items: payload }
default:
return state
}
}
export default productReducer
action.js
const products = axios.create({
baseURL: 'http://localhost:8001/api/products',
})
export const allProducts = () => async (dispatch) => {
console.log('fetching')
await products.get('/').then((res) => {
dispatch({
type: PRODUCTS_ALL,
payload: res.data,
})
})
}
And although I used connect() in my feed container
Feed.js
function Feed({ allProducts, product }) {
const [productItems, setProductItems] = useState()
const [loading, setLoading] = useState(false)
React.useEffect(() => {
allProducts()
console.log(product)
}, [])
return (
<div className='feed__content'>
{loading ? (
<Loader
type='Oval'
color='#212121'
height={100}
width={100}
/>
) : (
<div className='feed__products'>
<div className='feed__productsList'>
{product.map((product) => {
return (
<Product
name={product.name}
image={product.image}
price={product.price}
/>
)
})}
</div>
</div>
)}
</div>
)
}
const mapStateToProps = (state) => ({
product: state.product.items,
})
const mapDispatchToProps = (dispatch) => {
return {
allProducts: () => dispatch(allProducts()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Feed)
if you look at the data on the console you will see undefined but if I add a useEffect dependency it will create a loop , and the first of them is undefined and the rest are the data that I want.
because of this problem when I want to render products with map , it throws an error that said can't map undefiend .
How can I solve this problem
Joel Jaimon code worked well .
and in my code I added
const initialState = {
items: []
}
according to #Andrew and product?.map so my code works well now.
In Feed.js, you aren't getting the entire slice of state. You are trying to access the key item inside.
const mapStateToProps = (state) => ({
product: state.product.items,
})
Change your initialState to include that key and your code should be fine
const initialState = {
items: []
}
A/c to your code you're trying to make an async action in redux. You should use redux-saga or redux-thunk for this.
But you can achieve your output in the following way, without using redux-saga or thunk.
Modify your code in the following way:
Feed.js
function Feed({ allProducts, product }) {
// Set loading to true inititally
const [loading, setLoading] = useState(true);
React.useEffect(() => {
// Make your api call here once its complete and you get a res,
// dispatch the referred action with response as payload.
(async () => {
const products = axios.create({
baseURL: "http://localhost:8001/api/products",
});
const {data} = await products.get("/");
allProducts(data);
})()
// This call is only made when you load it for the first time, if it
// depends
// on something add that dependencies in the dependency array of //
// useEffect.
}, []);
// once store is updated with the passed payload. Set loading to
// false.
React.useEffect(() => {
if(product){
setLoading(false);
}
}, [product])
return (
<div className="feed__content">
{loading ? (
<Loader type="Oval" color="#212121" height={100} width={100} />
) : (
<div className="feed__products">
<div className="feed__productsList">
{product.map((product) => {
return (
<Product
name={product.name}
image={product.image}
price={product.price}
/>
);
})}
</div>
</div>
)}
</div>
);
}
const mapStateToProps = ({product}) => ({
product: product?.items,
});
// here we have a payload to pass
const mapDispatchToProps = (dispatch) => {
return {
allProducts: (payload) => dispatch(allProducts(payload)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Feed);
Action
export const allProducts = (payload) => ({
type: "PRODUCTS_ALL",
payload,
});
Reducer
const productReducer = (state = initialState, action) => {
const { type, payload } = action
switch (type) {
case "PRODUCTS_ALL":
return { ...state, items: payload }
default:
return state
}
}
export default productReducer
Problem statement: I would be giving you three really best methods, in order to solve it, the problem appears when redux is transferring states to the components, so it seems that the render is faster than the response of props to the component.
So, when you have props undefined by their value your application crashes.
note: I will be taking a general example and will show you how to avoid this error.
For example, I have a component in which I want to render the data from the redux response (with mapPropsToState), what I need to do is only to check it for undefined and then if it is undefined we need to use the conditional (ternary) operator for if and else statements.
//suppose you will get an address object from api ,which will contain city ,area ,street ,pin code etc
// address={} intially address is a blank object
render(){
return(
(typeof address.area!='undefined')?
<div>
<div>{address.city}</div>
<div>{address.street}</div>
<div>{address.pin_code}</div>
<div>{address.area}</div>
</div>
:<div>loading....</div>
)
}
And another way is to simply use the package of loadash in loadash you can achieve the same by calling the function "isUndefined"
you can also use it in the inputFields likewise
<FloatingLabel label="Username">
<Form.Control
type="input"
placeholder=" "
name="username"
value={props.username}
defaultValue={_.isUndefined(props.userEditResponse)?'':props.userEditResponse.username}
required
onChange={(e)=>onInputchange(e)}
/>
</FloatingLabel>
//note: you can also use (typeof props.userEditResponse!='undefined')?'do somthing':'leave it'
loadash installation:
npm i lodash
then in your component import the the below line and then you can use it, the way i used in the above example.
import _ from 'lodash'
note: the answer was just to give you an idea and this was really a panic problem that is why I shared it with you guys, it might help many of you, you can use a similar way in different situations. thanks!
furtherMore your can work with ?.:
The ?. operator is like the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.
This results in shorter and simpler expressions when accessing chained properties when the possibility exists that a reference may be missing. It can also be helpful while exploring the content of an object when there's no known guarantee as to which properties are required.
{ props.rolesListSuccessResponse?.permissions.map((list,index) => (
<div className="mb-3 col-md-3" key={index}>
<Form.Check
type={'checkbox'}
id={list.name}
label={list.name.charAt(0).toUpperCase()+list.name.slice(1)}
/>
</div>
))}
I am using React context to pass down data, following the docs, however I am stuck with the initial value and am not sure what I did wrong.
This is what my context file looks like:
export const ItemsContext = createContext([]);
ItemsContext.displayName = 'Items';
export const ItemsProvider = ({ children }) => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const setData = async () => {
setLoading(true);
setItems(await getItemsApiCall());
setLoading(false);
};
useEffect(() => {
setData();
}, []);
console.warn('Items:', items); // This shows the expected values when using the provider
return (
<ItemsContext.Provider value={{ items, loading }}>
{children}
</ItemsContext.Provider>
);
};
Now, I want to feed in those items into my app in the relevant components. I am doing the following:
const App = () => {
return (
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
);
}
However, as my comment states, items will always be empty. On the other hands, if I do just use the ItemsProvider, I do get the proper value, but at this point need to access it directly on the app, so the ItemsContext.Consumer seems to make more sense.
Am I doing something wrong here?
Edit: A way around it seems to be to wrap the Consumer with the Provider, but that feels wrong and didn't see that at the docs. Is that perhaps the case?
So essentially, something like this:
const App = () => {
return (
<ItemsProvider>
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
</ItemsProvider>
);
}
You have to provide a ItemsContext provider above the App component hierarchy,otherwise the default value of the context will be used.
something in this form:
<ItemsContext.Provider value={...}>
<App/>
</ItemsContext.Provider>
I'm trying to make a personal app and I'm running into some issues with the requests, nothing to do with the API.
The results of the request returns this JSON: https://pastebin.com/raw/vpdq2k6S
My code:
class Home extends Component {
componentDidMount() {
this.props.getTrending();
}
render() {
const { trending } = this.props.trending;
console.log(trending);
const Card = (props) => (
<Panel {...props} bordered header='Card title'>
{trending.results.map((i) => (
<React.Fragment key={i.id}>
<h6>{i.title}</h6>
</React.Fragment>
))}
</Panel>
);
return (
<div className='Home'>
<FlexboxGrid justify='center'>
<Card />
</FlexboxGrid>
</div>
);
}
}
export default connect(
(state) => {
return {
trending: state.trending,
};
},
{ getTrending }
)(Home);
My action:
import { GET_TRENDING } from "../types";
import axios from "axios";
export const getTrending = () => async (dispatch) => {
const res = await axios.get(
`https://api.themoviedb.org/3/movie/popular?api_key=KEY&language=en-US&page=1`
);
dispatch({
type: GET_TRENDING,
payload: res.data,
});
};
My reducer:
import { GET_TRENDING } from "../types";
const initialState = {
trending: [],
loading: true,
};
export default function trendingReducer(state = initialState, action) {
switch (action.type) {
case GET_TRENDING:
return {
...state,
trending: action.payload,
loading: false,
};
default:
return state;
}
}
Ignore the Card constant etc, that's related to an UI components library. You can see from the code that I'm logging the results of 'trending'. But I'm getting an empty array but here's the strange part:
If I change my map function from mapping through "trending.results" to "trending" and I refresh then the console returns an empty array and another array which is the correct one. If I change it back to "trending.results" then React auto-reloads the page returns me the correct array two times and it displays the data on the app but if I refresh the page without changing anything on the code then it goes back to showing an empty array and an error that says "cannot read property map of undefined" obviously cause somehow I'm not getting the correct data.
Anyone ever had this before? It makes absolutely no sense at all or if so then can someone guide me on how to solve this? I tried shutting down the React server completely and restarting it that wouldn't solve it. My brain is frozen (I can record a clip if required)
The answer is pretty simple. All you have to do is first getting the array from the api and then mapping through it. trending.results is not set so the error is shown. Cannot read property map of undefined
Go with a ternary operator:
{trending.results && trending.results.map((i) => (
<React.Fragment key={i.id}>
<h6>{i.title}</h6>
</React.Fragment>
))}
try this way.
export const getTrending = () => async (dispatch) => {
const res = await axios.get(
`https://api.themoviedb.org/3/movie/popular?api_key=KEY&language=en-US&page=1`
);
dispatch({
type: GET_TRENDING,
payload: res.data.results,
});
};
const Card = (props) => (
<Panel {...props} bordered header='Card title'>
{trending && trending.map((i) => (
<React.Fragment key={i.id}>
<h6>{i.title}</h6>
</React.Fragment>
))}
</Panel>
);
TL/DR: Having trouble referencing items in array by index (using React), could use some guidance.
I am attempting to create a component on my SPA out of data coming from an API. Using React hook useState and useEffect I have created state, done an axios call, and then set the response.data.articles to state (.articles is the array of objects I am using to create the dynamic content).
function App() {
const [storyArray, setStoryArray] = useState();
useEffect(() => {
axios.get('http://newsapi.org/v2/everything?domains=wsj.com&apiKey=[redacted_key_value]')
.then((response) => {
// console.log(response);
setStoryArray(response.data.articles);
})
.catch((err) => {
console.log(err);
})
}, [])
console.log(storyArray)
return (
<div className="App">
<Directory />
<HeaderStory />
</div>
);
}
From here, my state is an array of objects. My goal is to pass THE FIRST object as props to the component <HeaderStory /> but any time I attempt to reference this array item with dot notation I am met with an undefined error. My attempt at circumventing this is problem was to set the item to a variable and then pass the variable as props to the component.
const firstStory = storyArray[0];
This also resulted in an undefined error. Looking for advice / assistance on referencing items in an array to be passed and used in React.
On the first render the storyArray will have no value/undefined, The useEffect hook will execute only after component mount.
So you have to render the component conditionally, if the storyArray has value then only render the HeaderStory.
Example:
function App() {
const [storyArray, setStoryArray] = useState();
useEffect(() => {
axios.get('http://newsapi.org/v2/everything?domains=wsj.com&apiKey=[redacted_key_value]')
.then((response) => {
// console.log(response);
setStoryArray(response.data.articles);
})
.catch((err) => {
console.log(err);
})
}, [])
return (
<div className="App" >
<Directory />
{storyArray && <HeaderStory firstStory={storyArray[0]} />}
</div>
);
}
You should init default value for storyArray.
Example code:
function App() {
const [storyArray, setStoryArray] = useState([]); //Init storyArray value
useEffect(() => {
axios.get('http://newsapi.org/v2/everything?domains=wsj.com&apiKey=[redacted_key_value]')
.then((response) => {
// console.log(response);
setStoryArray(response.data.articles);
})
.catch((err) => {
console.log(err);
})
}, [])
console.log(storyArray)
return (
<div className="App">
<Directory />
<HeaderStory firstStory={storyArray[0] || {}} />
</div>
);
}
I set props firstStory={storyArray[0] || {}} because if storyArray[0] is undefined then pass empty object "{}" for firstStory prop.