I am having an issue where the null message (I dont have Partners) is flashing up for a second or so before the state is set and the message changes .. Is there any way to not show it until everything is resolved?
const Dashboard = () => {
const [partners, setPartners] = useState([]);
useEffect(() => {
async function getPartners() {
await axios.get('/api/partners').then(response => {
setPartners(response.data.data);
})
}
getPartners();
}, []);
return (
<>
{partners?.length ? <div><p>I have Partners</p></div> : <div><p>I dont have Partners</p></div>}
</>
)
}
export default Dashboard;
There are several approaches to achieve such a thing the simplest one is to use another state to indicate the HTTP request status. Here how it is can be implemented.
const Dashboard = () => {
const [partners, setPartners] = useState([]);
const [status, setStatus] = useState('idle');
useEffect(() => {
setStatus('pending');
async function getPartners() {
await axios.get('/api/partners').then(response => {
setPartners(response.data.data);
setStatus('resolved');
})
}
getPartners();
}, []);
return (
<>
{status === 'idle' || status === 'pending' ?
<LoadingComponent /> // A custom component to represent loading status
:
<>
{partners?.length ? <div><p>I have Partners</p></div> : <div><p>I dont have Partners</p></div>}
<>
}
</>
)
}
export default Dashboard;
Related
I recently learned about props, as I am fairly new to React, and am trying to implement them in places to reduce the amount of repeated code in each page of a web app I am building.
Originally, I had the following useEffect and useState placed above each return to simply control if something is available on the page or not.
const [waiting, setWaiting] = useState(false);
useEffect(() => {
setWaiting(true)
}
return (
<>
{ waiting ? ( *displays a "Coming Soon" page* ) : ( *displays the webpage* )
</>
)
Which ran well and I could simply change the boolean value on the useEffect if I wanted to remove the waiting screen and display the page content.
But this resulted in me repeating that useEffect for every page that had content, which is enough for me to realize I don't have to repeat this code.
So I set up the following component:
import { useState, useEffect } from "react";
const NoPhotos = (props) => {
//Photos Aren't Ready Yet
const [waiting, setWaiting] = useState(false);
useEffect(() => {
if (waiting) {
setWaiting(true);
} else {
setWaiting(false);
}
}, [props.wait, waiting]);
};
export default NoPhotos;
And I replace the "waiting" from the original ternary operator, with the NoPhotos component, as follows:
return (
<>
{ <NoPhotos wait={true} ? ( *displays a "Coming Soon" page* ) : ( *displays the webpage* )
</>
)
But I noticed that regardless of what boolean I put in the "wait={}", it doesn't change and always shows "true".
Is there a way to make it so the boolean value that is passed in "wait=" controls the state?
A hook like this can be helpful:
function useWait(setWaiting) {
useEffect(() => {
setTimeout(() => {
setWaiting(false);
}, 1000);
}, [setWaiting]);
}
Usage:
const [waiting, setWaiting] = useState(true);
useWait(setWaiting);
return (
<>
<p>{waiting ? "Coming Soon" : "the webpage"}</p>
</>
);
But I would rather use something like this:
function useWait() {
const [waiting, setWaiting] = useState(true);
useEffect(() => {
setTimeout(() => {
setWaiting(false);
}, 1000);
}, []);
return waiting;
}
or this:
function Wait({ loader, content }) {
const [waiting, setWaiting] = useState(true);
useEffect(() => {
setTimeout(() => {
setWaiting(false);
}, 1000);
}, []);
return <>{waiting ? loader : content}</>;
}
Usage:
const waiting = useWait(true);
return (
<>
<p>{waiting ? "Coming Soon" : "the webpage"}</p>
<p>
<Wait loader="Coming soon" content="the webpage" />
</p>
</>
);
I am having a data rendering issue in react. Somehow, data is not automatically updated after it's updated in the server side. I can't put all the code in here, cuz the code is kind of lengthy. so i pasted/renamed some variables. Even if some variables are missing, please understand. Basically, I have a button on the page and when the button is clicked, the status changes to 'UPLOADING' and the function checkIfDataExists is called to fetch data from the server side and data should be automatically updated without page refresh, but when I test this, data is successfully retrieved from the server side, but the updated data is not rendered. I see 'successful...' on the Console. Is there anything wrong?
const Settings: React.FC<IProps> = props => {
const { orgId } = props
const password = 'dummy'
const { data } = httpCall(`/${orgId}/${userId}/settings`)
return (
<div>
{data && <SettingsForm data={data} password={password} {...props} />}
</div>
)
}
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
data.modeUsername = value.modeUsername
data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{data.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{data.modePassword}</p>
</div>
The problem I see is that after you setInterval an API you didn't set in the state to trigger the component to rerender. You don't need to be explicit to define resData to data because if you define data already useState already it types.
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [resdata,setResData] = useState(data)
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
setResData({
modeUsername: value.modeUsername,
modePassword: value.modePassword,
})
// data.modeUsername = value.modeUsername
// data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{resdata.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{resdata.modePassword}</p>
</div>
I'm using react-hook-form library with a multi-step-form
I tried getValues() in useEffect to update a state while changing tab ( without submit ) and it returned {}
useEffect(() => {
return () => {
const values = getValues();
setCount(values.count);
};
}, []);
It worked in next js dev, but returns {} in production
codesandbox Link : https://codesandbox.io/s/quirky-colden-tc5ft?file=/src/App.js
Details:
The form requirement is to switch between tabs and change different parameters
and finally display results in a results tab. user can toggle between any tab and check back result tab anytime.
Implementation Example :
I used context provider and custom hook to wrap setting data state.
const SomeContext = createContext();
const useSome = () => {
return useContext(SomeContext);
};
const SomeProvider = ({ children }) => {
const [count, setCount] = useState(0);
const values = {
setCount,
count
};
return <SomeContext.Provider value={values}>{children}</SomeContext.Provider>;
};
Wrote form component like this ( each tab is a form ) and wrote the logic to update state upon componentWillUnmount.
as i found it working in next dev, i deployed it
const FormComponent = () => {
const { count, setCount } = useSome();
const { register, getValues } = useForm({
defaultValues: { count }
});
useEffect(() => {
return () => {
const values = getValues(); // returns {} in production
setCount(values.count);
};
}, []);
return (
<form>
<input type="number" name={count} ref={register} />
</form>
);
};
const DisplayComponent = () => {
const { count } = useSome();
return <div>{count}</div>;
};
Finally a tab switching component & tab switch logic within ( simplified below )
const App = () => {
const [edit, setEdit] = useState(true);
return (
<SomeProvider>
<div
onClick={() => {
setEdit(!edit);
}}
>
Click to {edit ? "Display" : "Edit"}
</div>
{edit ? <FormComponent /> : <DisplayComponent />}
</SomeProvider>
);
}
so I am trying to update my parent component whenever I scroll to the bottom. When I do scroll to the bottom, I am calling fetchData, which is in the parent component and is a dependency in the useEffect, however, the component does not update, leaving me with data from the first component render.
The first 7 sets of data is fetched successfully, and when I scroll to the bottom, fetchData is called again, however the component does not update, however when I then click save on the parent component, then the next sets of data is successfully loaded and displayed to the screen as it should. I feel I am doing something wrong i'm just clueless as to what it is. Any help would be very much appreciated
This is the parent component:
function Following({route, navigation}) {
const [followingData, setfollowingData] = useState();
const [loading, setLoading] = useState(true);
const [lastVisible, setLastVisible] = useState(0);
const fetchData = useCallback(() => {
const dataRef = firestore().collection('usernames');
dataRef
.doc(route.params.username.toLowerCase())
.collection('Following')
.orderBy('followedAt')
.startAfter(lastVisible)
.limit(7)
.onSnapshot((snapshot) => {
const last = snapshot.docs[snapshot.docs.length - 1];
setLastVisible(last.data().followedAt);
let promises = [];
snapshot.forEach((doc) => {
const data = doc.data();
promises.push(
data.path.get().then((res) => {
const userData = res.data();
return {
profileName: doc.id ? doc.id : null,
displayName: userData.displayName ? userData.displayName : null,
followerCount:
userData.followers !== undefined ? userData.followers : 0,
followingCount:
userData.following !== undefined ? userData.following : 0,
imageUrl: userData.imageUrl ? userData.imageUrl : null,
};
}),
);
});
Promise.all(promises).then((res) => {
setfollowingData(res);
});
setLoading(false);
});
}, []);
useEffect(() => {
const dataRef = firestore().collection('usernames');
const cleanup = dataRef
.doc(route.params.username.toLowerCase())
.collection('Following')
.onSnapshot(fetchData);
return cleanup;
}, [route.params.username, fetchData]);
return (
<>
{loading ? (
<ActivityIndicator size="large" color="black" />
) : (
<>
<FolloweringScreens
data={followingData}
screen={'Following'}
username={route.params.username}
navigation={navigation}
fetchData={fetchData}
/>
</>
)}
</>
);
}
export default Following;
And the child component is:
function FolloweringScreens({
data,
screen,
username,
navigation,
fetchData
}) {
return (
<>
<FlatList
scrollEnabled={true}
onEndReachedThreshold={0}
onEndReached={fetchData}
data={data}
keyExtractor={(i, index) => index.toString()}
renderItem={({item, index}) => {
return (
<>
(`All my data being used here`)
</>
);
}}
/>
</>
);
}
export default FolloweringScreens;
Im just wondering how i can stop the function from returning before the state has been updated - Here is my code so i can explain more clearly.
const mockData = {
current: {
temperature: 'loading'
}
}
export default function Weather({ city }) {
const [data, setData] = useState(mockData)
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
})
}, [])
return (
<div>
<h1>Weather</h1>
<p>temperature: {data.current.temperature}</p>
</div>
)
}
Right now i am using mockData because if i dont i will get an error because the .current.temperature properties do not exist (because set state hasnt been updated yet).
How can i stop the error and stop the div being returned before the set state has been updated or atleast stop the error and return an empy div or something.
What you can do is add a conditional within your return.
Try this:
return (
<div>
<h1>Weather</h1>
<p>Temperature: {(data && data.current) ? data.current.temperature : ""}</p>
</div>
)
You can also use optional chaining to achieve the same result.
return (
<div>
<h1>Weather</h1>
<p>Temperature: {data?.current?.temperature || ""}</p>
</div>
)
You need to check that data exists before you reference data.current, and you need to check that data.current exists before you reference data.current.temperature. If you access a property of undefined, your code will crash.
You need to have some additional state, such as isLoading
If you want a hack, you can do this:
export default function Weather({ city }) {
const [data, setData] = useState(mockData)
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
})
}, [])
function renderTemperature() {
if (!data) {
return null;
} else if (data && !data.current) {
return null;
} else if (data.current && data.current.temperature) {
return <p>temperature: {data.current.temperature}</p>
}
}
return (
<div>
<h1>Weather</h1>
{ renderTemperature() }
</div>
)
}
Much better solution:
export default function Weather({ city }) {
const [data, setData] = useState(null)
const [isLoaded, setIsLoaded] = useState(false);
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
setIsLoaded(true);
})
}, [])
function renderTemperature() {
}
return (
<div>
<h1>Weather</h1>
{ isLoaded ? <p>temperature: {data.current.temperature}</p> : null}
</div>
)
}